library(dplyr)
library(ggplot2)
library(ggpubr)
library(emmeans)
library(ggsignif)
library(here)
library(tidyverse)
library(grid)
library(PNWColors)

theme_eb <- theme_classic(base_size = 16) +
  theme(
    plot.title = element_text(size = 20),
    axis.text.x = element_text(size = 14),
    axis.text.y = element_text(size = 14)
  )




theme_set(theme_eb)
pal3 <- pnw_palette("Sunset", 3, type=c("discrete"))
pal7 <-  pnw_palette("Starfish", 7, type=c("discrete"))

here::here()
[1] "/Users/emma/Library/CloudStorage/OneDrive-SharedLibraries-IndianaUniversity/Lennon, Jay - 0000_Bueren/Projects/LifeStyle/PhageLifestyleSporulation"
### note the quartz script needs to be fixed, the headers are janky and wrong (had to be manually fixed)



ParS <- read.delim2(here("02_motifs/ParS/data/01_out_01_indiv_hmm1_ParS.tsv"))


ParS$q.value<- as.numeric(ParS$q.value)
ParS$p.value<- as.numeric(ParS$p.value)
ParS$score<- as.numeric(ParS$score)



### remove any duplicate motifs (occasionally a motif will be a palindromic and hit both + and - strands) 
ParS <- ParS %>%
  group_by(sequence_name, start, stop) %>%  # group by coordinates
  slice_max(order_by = score, n = 1) %>%    # keep only the row with highest score
  ungroup()

### phi3T KY030782, spbeta AF020713
## redrock (actino phage with ParABS) GU339467, uses parS sites but of a different type than sporulation ParS

## combine phage metadata with ParS hits. Label any phage that had no ParS hits with "no_hit" in metadata
inph <- read.csv(here("00_data", "inphared_db", "14Apr2025_knownsporestatus.csv"), row.names = 1)
inph$lifestyle <- ifelse(inph$lifestyle=="Temp", "Temperate", inph$lifestyle)
inph$bac.host <- ifelse(inph$sporulation=="Spor", "Sporulating Bacillota", "Nonsporulating Bacillota")  #"Asporogenous Bacillota")
inph$bac.host <- ifelse(inph$newgtdb_Phylum=="Bacillota", inph$bac.host, "Non-Bacillota")

inph <- unite(inph, nice, c("lifestyle", "bac.host"), sep=" Phages of ", remove = FALSE )


meta.cats <- unique(select(inph, host_phage_spor, bac.host, lifestyle, nice, sporulation))



#inph <- select(inph, Accession, Host, Genome.Length..bp., gtdb_f, f_spor, newgtdb_Phylum,  host_phage_spor, phage_type, sporulation, lifestyle, nice)
all <- merge(inph, ParS, by.x="Accession", by.y="sequence_name", all.x=TRUE, all.y=FALSE)
all$motif_id[is.na(all$motif_id)] <- "no_hit"

### create binary hit column of 1 for ParS hit, 0 if no hit
all$hit <- ifelse(all$motif_id=="ParS_BSub", 1, 0)
all$q.value[is.na(all$q.value)] <- 1

### remove duplicates (keep only reference strains)
### currently this only works on bacilliota, 99.5% ANI over 100% alignment threshold
## rest of inphared is pending 

all <- subset(all, all$ref=="not_run" | all$ref=="ref_seq")

threshold testing



### threshold testing

library(dplyr)
library(ggplot2)

# Define thresholds
thresholds <- 10^seq(-6, -1, by = 1)   # from 1e-6 to 1e-1
thresholds <- c(thresholds, 0.05, 1)      # add 0.05 explicitly if desired
results <- lapply(thresholds, function(th) {
  all %>%
    group_by(Accession, host_phage_spor) %>%
    summarise(
      pos.ParS.hit = max(ifelse(!is.na(q.value) & q.value <= th, 1, 0)),
      .groups = "drop"
    ) %>%
    group_by(host_phage_spor) %>%
    summarise(
      Phage_has_ParS = sum(pos.ParS.hit),
      total.phage = n(),
      .groups = "drop"
    ) %>%
    mutate(
      ParS.pos.perc = Phage_has_ParS / total.phage * 100,
      threshold = th
    )
}) %>% bind_rows()

results.fig <- merge(results, meta.cats, by="host_phage_spor")

# Plot
p<- ggplot(results.fig, aes(x = threshold, y = ParS.pos.perc, color = bac.host, linetype = lifestyle)) +
  geom_line(size = 1.2) +
  geom_point() +
  scale_x_log10() +  # log scale for q-values
  labs(
    x = "False Positive (q-value) Threshold",
    y = "% Phages with 1 or more ParS Binding Site"
  ) + labs(linetype = "Phage Lifestyle", color="Bacterial Host") +geom_vline(xintercept = 1e-4, linetype = "dotted") #+ theme(legend.position="none")
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.
p + theme(legend.position = c(0.125, 0.82)) + scale_color_manual(values = rev(pal3))
Warning: A numeric `legend.position` argument in `theme()` was deprecated in ggplot2 3.5.0.
Please use the `legend.position.inside` argument of `theme()` instead.
ggsave(here("02_motifs/ParS/QValueTresh1e4_line.png"),width=7, height=7)

Filter w/ q value and summarize data



#### Q FILTERING
ParS.qual <- all

#ParS.qual$hit <- ifelse(ParS.qual$q.value<1e-4, 1, 0)
ParS.qual$hit <- ifelse(ParS.qual$q.value<1e-4, 1, 0)

## create binary list of phages w/ and w/out ParS hits
ParS.pos <- ParS.qual %>% group_by(Accession, host_phage_spor) %>%
  summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>% 
ParS.pos %>% count(host_phage_spor)

## create binary list of phages w/ and w/out ParS hits
ParS.sanity <- ParS.qual %>% group_by(Accession, host_phage_spor, Host, Description, Lowest.Taxa, Genus, Family, Genome.Length..bp., molGC....) %>%
  summarise(total.ParS.hits = sum(hit), pos.ParS.hit = max(hit), .groups = "drop") #%>% 

only.pos <- subset(ParS.sanity, ParS.sanity$total.ParS.hits>0)


ParS.pos %>% count(host_phage_spor)

ParS.summary <- ParS.pos %>% 
  group_by(host_phage_spor) %>% 
  summarise(
    total_ParS_hits = sum(total.ParS.hits, na.rm = TRUE),
    Phage_has_ParS = sum(pos.ParS.hit, na.rm = TRUE),
    total.phage = n(),
    .groups = "drop"
  ) %>% 
  mutate(ParS.pos.perc = Phage_has_ParS / total.phage * 100)


ParS.bin <- ParS.pos[,c(1,2,4)]

ParS positional analysis



ParS.hits <- subset(ParS.qual, ParS.qual$hit==1)

## find center phage genome (whole genome /2)
ParS.hits$seq.mdpt <- as.numeric(ParS.hits$Genome.Length..bp.)/2

## find center of motif 
ParS.hits$motif.mdpt <- (ParS.hits$stop + ParS.hits$start )/ 2

### subtract midpoint motif from sequence midpoint to see how far away they are
ParS.hits <- ParS.hits %>%
  mutate(mdpt.align = (motif.mdpt - seq.mdpt))

ParS.hits$mdpt.align.kbp <- ParS.hits$mdpt.align/1000

#### TO GET RELATIVE motif alignmen

# Relative position as fraction of genome length
# (-0.5 = start, 0 = center, +0.5 = end)
ParS.hits$rel.mdpt <- (ParS.hits$motif.mdpt - ParS.hits$seq.mdpt) / ParS.hits$Genome.Length..bp.



# Relative position from genome start (0 to 1)
ParS.hits$rel.frac <- ParS.hits$motif.mdpt / ParS.hits$Genome.Length..bp.

# Optionally convert to percentage
ParS.hits$rel.percent <- ParS.hits$rel.frac * 100

##Set specific order for bacterial hosts to appear on graphs
ParS.hits$nice <- factor(ParS.hits$nice, levels = c('Lytic Phages of Sporulating Bacillota', 'Temperate Phages of Sporulating Bacillota', 'Lytic Phages of Nonsporulating Bacillota', 'Temperate Phages of Nonsporulating Bacillota', 'Lytic Phages of Non-Bacillota', 'Temperate Phages of Non-Bacillota' ),ordered = TRUE)


ggplot(ParS.hits, aes(x = mdpt.align.kbp, fill = nice)) +
  geom_histogram(binwidth = 5, position = "dodge") +
  geom_vline(xintercept = 0, linetype = "dotted") +
  scale_x_continuous(breaks = seq(-90, 90, by = 30)) +
  labs(x = "Motif distance (kb) from center of phage genome", y = "Motif count") +
  facet_wrap(~ nice, ncol = 2)+ ggtitle("Absolute Position of ParS Motif on Phage Genomes") + theme(legend.position="none")+ scale_fill_manual(values = pal7)


ggsave(here("02_motifs/ParS/figs/AbsoluteParSposition_1e4.png"), width=7, height=7)


ggplot(ParS.hits, aes(x = rel.mdpt, fill = nice)) +
  geom_histogram(binwidth = 0.05, position = "dodge") +
  geom_vline(xintercept = 0, linetype = "dotted") +
  scale_x_continuous(breaks = seq(-0.5, 0.5, by = 0.25)) +
  labs(x = "Motif position relative to center of phage genome", y = "Motif count") +
  facet_wrap(~ nice, ncol = 2) + ggtitle("Relative Position of ParS Motif on Phage Genomes")+ theme(legend.position="none") + scale_fill_manual(values = pal7)


ggsave(here("02_motifs/ParS/figs/RelativeParSposition_1e4.png"), width=7, height=7)

Enrichment

# -----------------------------
pairwise_fisher_both_directions <- function(df, group_col, hits_col, total_col, p.adjust.method = "BH") {
  
  groups <- unique(df[[group_col]])
  combs <- combn(groups, 2, simplify = FALSE)
  
  results <- data.frame(
    group1 = character(),
    group2 = character(),
    odds_ratio = numeric(),
    conf_low = numeric(),
    conf_high = numeric(),
    p.value = numeric(),
    p.adj = numeric(),
    stringsAsFactors = FALSE
  )
  
  pvals <- numeric()
  
  for (c in combs) {
    g1 <- df[df[[group_col]] == c[1], ]
    g2 <- df[df[[group_col]] == c[2], ]
    
    tab <- matrix(c(
      g1[[hits_col]], g1[[total_col]] - g1[[hits_col]],
      g2[[hits_col]], g2[[total_col]] - g2[[hits_col]]
    ), nrow = 2, byrow = TRUE)
    
    rownames(tab) <- c(c[1], c[2])
    colnames(tab) <- c("Present", "Absent")
    
    ft <- fisher.test(tab)
    
    # group1 vs group2
    results <- rbind(results, data.frame(
      group1 = c[1],
      group2 = c[2],
      odds_ratio = ft$estimate,
      conf_low = ft$conf.int[1],
      conf_high = ft$conf.int[2],
      p.value = ft$p.value,
      p.adj = NA
    ))
    
    # group2 vs group1 (inverse OR)
    results <- rbind(results, data.frame(
      group1 = c[2],
      group2 = c[1],
      odds_ratio = 1 / ft$estimate,
      conf_low = 1 / ft$conf.int[2],
      conf_high = 1 / ft$conf.int[1],
      p.value = ft$p.value,
      p.adj = NA
    ))
    
    pvals <- c(pvals, ft$p.value, ft$p.value)
  }
  
  results$p.adj <- p.adjust(pvals, method = p.adjust.method)
  return(results)
}
# -----------------------------
# Compute Fisher tests
t <- pairwise_fisher_both_directions(
  df = ParS.summary,
  group_col = "host_phage_spor",
  hits_col = "Phage_has_ParS",
  total_col = "total.phage",
  p.adjust.method = "BH"
)

# Specify pairs of interest (with direction)
selected_pairs <- list(
  c("OtherPhyla_Lytic_NonSpor", "OtherPhyla_Temp_NonSpor"), 
  c("Bacillota_Lytic_NonSpor", "Bacillota_Temp_NonSpor"),
  c("Bacillota_Temp_Spor", "Bacillota_Temp_NonSpor"),
  c("Bacillota_Lytic_Spor", "OtherPhyla_Lytic_NonSpor"),
  c("Bacillota_Lytic_Spor", "Bacillota_Lytic_NonSpor"),
  c("Bacillota_Lytic_Spor", "Bacillota_Temp_Spor")
)

pairs_df <- do.call(rbind, lapply(selected_pairs, function(x) {
  data.frame(group1 = x[1], group2 = x[2], stringsAsFactors = FALSE)
}))



pairwise_res_subset <- t %>%
  semi_join(pairs_df, by = c("group1", "group2")) 



pairwise_res_subset <- pairwise_res_subset %>%
  mutate(
    label_combined = ifelse(
      p.adj < 0.05,
      ifelse(
        p.adj < 0.0001,
        paste0("OR=", round(odds_ratio, 2), ", p<0.0001"),
        paste0("OR=", round(odds_ratio, 2), ", p=", signif(p.adj, 3))
      ),
      "n.s."
    )
  )
# -----------------------------
# Factor order
levels_order <- c(
  'Bacillota_Lytic_Spor',
  'Bacillota_Temp_Spor',
  'Bacillota_Lytic_NonSpor',
  'Bacillota_Temp_NonSpor',
  'OtherPhyla_Lytic_NonSpor',
  'OtherPhyla_Temp_NonSpor'
)


df_all <- ParS.pos

# Sample sizes
n_counts <- df_all %>%
  group_by(host_phage_spor) %>%
  summarise(
    positive = sum(pos.ParS.hit == 1, na.rm = TRUE),
    total    = n(),
    .groups = "drop"
  ) %>%
  mutate(
    perc  = round(100 * positive / total, 1),
    label = paste0(positive, "/", total, " (", perc, "%)")
  )

# Base plot
pd <- position_jitter(width = 0.25, height = 0.05)

vp <- ggplot(df_all, aes(x = host_phage_spor, y = pos.ParS.hit, color = host_phage_spor)) +
  geom_point(
    aes(fill = host_phage_spor),
    position = pd,
    size = 2,
    alpha = 0.6,
    shape = 21,
    color = "black",
    stroke = 0.3
  ) +
  scale_y_continuous(
    #limits = c(-0.5, 10),  # make room for bars above
    breaks = c(0, 1),
    labels = c("Absent", "Present")
  ) +
  scale_x_discrete(limits = levels_order, drop = FALSE, labels = c(
      'Bacillota_Lytic_Spor'    = 'Lytic',
      'Bacillota_Temp_Spor'     = 'Temperate',
      'Bacillota_Lytic_NonSpor' = 'Lytic',
      'Bacillota_Temp_NonSpor'  = 'Temperate',
      'OtherPhyla_Lytic_NonSpor' = 'Lytic',
      'OtherPhyla_Temp_NonSpor'  = 'Temperate'
    )) +
  coord_cartesian(clip = "off") +
  theme(
    legend.position = "none",
    plot.margin = margin(10, 10, 30, 10)
  ) +
  xlab("") + ylab("Presence of ParS in phage genomes") +
  geom_text(
    data = n_counts,
    aes(x = host_phage_spor, y = -0.3, label = paste0("n=", label)),
    inherit.aes = FALSE,
    size = 4
  )
# -----------------------------
spor_label    <- textGrob("Sporulating Bacilliota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 1/6, y = -0.08, just = "center")
nonspor_label <- textGrob("Non-Sporulating Bacilliota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 3/6, y = -0.08, just = "center")
other_label   <- textGrob("Non-Bacilliota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 5/6, y = -0.08, just = "center")

vp <- vp + annotation_custom(spor_label) + annotation_custom(nonspor_label) + annotation_custom(other_label)


pairwise_res_subset$group1 <- factor(pairwise_res_subset$group1, levels = levels_order, ordered = TRUE)
pairwise_res_subset <- pairwise_res_subset %>%
  arrange(factor(group1, levels = levels_order),
          factor(group2, levels = levels_order)) %>%
  mutate(y.position = max(df_all$pos.ParS.hit, na.rm = TRUE) + 
           0.5 + (nrow(.) - row_number()) * 0.3)



# Add significance bars
vp_fisher <- vp +
  stat_pvalue_manual(
    pairwise_res_subset,
    label = "label_combined",
    hide.ns = FALSE,
    tip.length = 0.02,
    size = 3.5
  )

# Show plot
vp_fisher+ scale_fill_manual(values = pal7)


ggsave(here("02_motifs/ParS/figs/ParS_Enrichment_1e4.png"), width=7, height=7)

Kruskal
# -----------------------------
# Count positives and totals per group
n_counts <- df_all %>%
  group_by(host_phage_spor) %>%
  summarise(
    positive = sum(total.ParS.hits > 0, na.rm = TRUE),
    total    = n(),
    .groups = "drop"
  ) %>%
  mutate(
    perc  = round(100 * positive / total, 1),
    label = paste0(positive, "/", total, " (", perc, "%)")
  )

# -----------------------------
kruskal_res <- kruskal.test(total.ParS.hits ~ host_phage_spor, data = df_all)
#print(kruskal_res)

# -----------------------------
# Define comparisons
comparisons_list <- combn(unique(df_all$host_phage_spor), 2, simplify = FALSE)

# Bar placement settings
offset <- 1     # push bars higher than max data
y_step <- 1.5   # vertical spacing
y_start <- max(df_all$total.ParS.hits, na.rm = TRUE) + offset

# Compute raw p-values
pairwise_res <- lapply(seq_along(comparisons_list), function(i){
  comp <- comparisons_list[[i]]
  x <- df_all$total.ParS.hits[df_all$host_phage_spor == comp[1]]
  y <- df_all$total.ParS.hits[df_all$host_phage_spor == comp[2]]
  wt <- wilcox.test(x, y)
  data.frame(
    group1 = comp[1],
    group2 = comp[2],
    p.value = wt$p.value,
    y.position = y_start + (i - 1) * y_step
  )
}) %>% bind_rows()

# Adjust p-values (BH)
pairwise_res$p.adj <- p.adjust(pairwise_res$p.value, method = "BH")

# Label formatting
pairwise_res <- pairwise_res %>%
  mutate(
    label = case_when(
      p.adj < 0.0001 ~ "p < 0.0001",
      p.adj < 0.05   ~ paste0("p = ", signif(p.adj, 3)),
      TRUE           ~ "n.s."
    )
  )

# -----------------------------
levels_order <- c(
  'Bacillota_Lytic_Spor',
  'Bacillota_Temp_Spor',
  'Bacillota_Lytic_NonSpor',
  'Bacillota_Temp_NonSpor',
  'OtherPhyla_Lytic_NonSpor',
  'OtherPhyla_Temp_NonSpor'
)

visualize


library(dplyr)
library(ggplot2)
library(rstatix)

Attaching package: ‘rstatix’

The following object is masked from ‘package:stats’:

    filter
# --- Factor setup ---
levels_order <- c(
  'Bacillota_Lytic_Spor',
  'Bacillota_Temp_Spor',
  'Bacillota_Lytic_NonSpor',
  'Bacillota_Temp_NonSpor',
  'OtherPhyla_Lytic_NonSpor',
  'OtherPhyla_Temp_NonSpor'
)

df_all <- ParS.pos %>%
  mutate(host_phage_spor = factor(host_phage_spor, levels = levels_order))

df_violin <- df_all %>%
  filter(total.ParS.hits > 0)

# --- n counts ---
n_counts <- df_all %>%
  group_by(host_phage_spor) %>%
  summarise(
    positive = sum(total.ParS.hits > 0, na.rm = TRUE),
    total    = n(),
    .groups = "drop"
  ) %>%
  mutate(
    perc  = round(100 * positive / total, 1),
    label = paste0(positive, "/", total, " (", perc, "%)")
  )

# --- Stats ---
# Kruskal–Wallis
kw_res <- kruskal_test(df_all, total.ParS.hits ~ host_phage_spor)

# Pairwise Wilcoxon with BH correction
wilc_test <- df_all %>%
  pairwise_wilcox_test(total.ParS.hits ~ host_phage_spor,
                       p.adjust.method = "BH") #%>%

wilc_test <- wilc_test %>% unite(col = "test", c("group1", "group2"), remove = FALSE, sep = "_")


### subset tests you want to show
wilc_test <- subset(wilc_test, wilc_test$test=="Bacillota_Lytic_Spor_Bacillota_Temp_Spor" |
                      wilc_test$test=="Bacillota_Lytic_Spor_Bacillota_Lytic_NonSpor" |
                      wilc_test$test=="Bacillota_Lytic_Spor_OtherPhyla_Lytic_NonSpor" |
                      wilc_test$test=="Bacillota_Temp_Spor_Bacillota_Temp_NonSpor" |
                      wilc_test$test=="Bacillota_Temp_Spor_OtherPhyla_Temp_NonSpor" |
                      wilc_test$test=="Bacillota_Lytic_NonSpor_Bacillota_Temp_NonSpor" |
                      wilc_test$test=="OtherPhyla_Lytic_NonSpor_OtherPhyla_Temp_NonSpor")



max_y <- max(df_all$total.ParS.hits, na.rm = TRUE)
step <- .85
wilc_test <- wilc_test %>%
  mutate(y.position = max_y + rev(seq(1, nrow(.)))*step)


# Make label: OR not relevant here (Wilcoxon), so p only
wilc_test <- wilc_test %>%
  mutate(
    p_label = ifelse(p.adj < 0.0001, "p < 0.0001",
              ifelse(p.adj < 0.05, paste0("p=", signif(p.adj, 3)), "n.s."))
  )



  
# --- Plot ---
pd <- position_jitter(width = 0.25, height = 0.25)

vp <- ggplot(df_all, aes(x = host_phage_spor,
                         y = total.ParS.hits,
                         color = host_phage_spor)) +
  # Violin for positives
  geom_violin(
    data = df_violin,
    inherit.aes = TRUE,
    color = "black",
    fill = NA,
    scale = "width",
    width = 0.8,
    trim = TRUE
  ) +
  # Jittered points
  geom_point(
    aes(fill = host_phage_spor),
    position = pd,
    size = 2,
    alpha = 0.5,
    shape = 21,
    color = "black",
    stroke = 0.3
  ) +
  # Total n
  geom_text(
  data = n_counts,
  aes(
    x = host_phage_spor,
    y = -0.75,   # slightly below 0; adjust as needed
    label = label
  ),
  inherit.aes = FALSE,
  size = 3,
  vjust = 1  # align text above the y position
) +
  # Add significance bars
  stat_pvalue_manual(
    wilc_test,
    label = "p_label",
    hide.ns = FALSE,
    tip.length = 0.02,
    size = 3.5
  ) +
  # Axis order
  scale_x_discrete(limits = levels_order, drop = FALSE, labels = c(
      'Bacillota_Lytic_Spor'    = 'Lytic',
      'Bacillota_Temp_Spor'     = 'Temperate',
      'Bacillota_Lytic_NonSpor' = 'Lytic',
      'Bacillota_Temp_NonSpor'  = 'Temperate',
      'OtherPhyla_Lytic_NonSpor' = 'Lytic',
      'OtherPhyla_Temp_NonSpor'  = 'Temperate'
    )) +
  # Give room for n labels and bars
  #expand_limits(y = max(df_all$total.ParS.hits, na.rm = TRUE) * 4) +
  coord_cartesian(clip = "off") +
  theme(legend.position = "none",
        plot.margin = margin(10, 10, 30, 10)) +
  xlab("") + scale_y_continuous(limits=c(0,12.5,2),  breaks=c(0,2,4,6,8))
#vp #+ expand_limits(y = c(-1, max(df_all$total.ParS.hits) * 1.2))


spor_label    <- textGrob("Sporulating Bacilliota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 1/6, y = -0.08, just = "center")
nonspor_label <- textGrob("Non-Sporulating Bacilliota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 3/6, y = -0.08, just = "center")
other_label   <- textGrob("Non-Bacilliota Host", gp = gpar(fontsize = 14, fontface = "bold"), x = 5/6, y = -0.08, just = "center")

vp.krusk <- vp + annotation_custom(spor_label) + annotation_custom(nonspor_label) + annotation_custom(other_label)+ scale_fill_manual(values = pal7)


vp.krusk

ggsave(here("02_motifs/ParS/figs/ParS_Hits_Kruskal_1e4.png"), width=7, height=7)

EMMEANS


analyze_enrichment <- function(df, ref_treatment = "Enriched") {
  # relevel the treatment factor
  df$host_phage_spor <- relevel(factor(df$host_phage_spor), ref = ref_treatment)
  
  # logistic regression: motif presence ~ treatment
  model <- glm(pos.ParS.hit ~ host_phage_spor, family = binomial, data = df)
  
  # estimated probabilities per treatment
  emm <- emmeans(model, ~ host_phage_spor, type = "response")
  
  list(
    model_summary = summary(model),
    probabilities = emm,
    pairwise_tests = pairs(emm, adjust = "tukey")
  )
}
library(emmeans)
res <- analyze_enrichment(ParS.bin, ref_treatment = "Bacillota_Lytic_Spor")

res$model_summary     # logistic regression coefficients

Call:
glm(formula = pos.ParS.hit ~ host_phage_spor, family = binomial, 
    data = df)

Coefficients:
                                        Estimate Std. Error z value Pr(>|z|)    
(Intercept)                              -1.8736     0.1638 -11.439  < 2e-16 ***
host_phage_sporBacillota_Lytic_NonSpor   -2.6744     0.3730  -7.170 7.48e-13 ***
host_phage_sporBacillota_Temp_NonSpor    -3.6431     0.4771  -7.636 2.24e-14 ***
host_phage_sporBacillota_Temp_Spor       -0.7655     0.2640  -2.900  0.00373 ** 
host_phage_sporOtherPhyla_Lytic_NonSpor  -2.6808     0.1914 -14.005  < 2e-16 ***
host_phage_sporOtherPhyla_Temp_NonSpor   -2.7620     0.2031 -13.599  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 2731.1  on 19984  degrees of freedom
Residual deviance: 2537.2  on 19979  degrees of freedom
AIC: 2549.2

Number of Fisher Scoring iterations: 8
res$probabilities     # estimated motif probability per treatment
 host_phage_spor             prob      SE  df asymp.LCL asymp.UCL
 Bacillota_Lytic_Spor     0.13313 0.01890 Inf   0.10024   0.17472
 Bacillota_Lytic_NonSpor  0.01048 0.00347 Inf   0.00546   0.02001
 Bacillota_Temp_NonSpor   0.00400 0.00179 Inf   0.00167   0.00958
 Bacillota_Temp_Spor      0.06667 0.01290 Inf   0.04544   0.09680
 OtherPhyla_Lytic_NonSpor 0.01041 0.00102 Inf   0.00859   0.01261
 OtherPhyla_Temp_NonSpor  0.00961 0.00114 Inf   0.00761   0.01213

Confidence level used: 0.95 
Intervals are back-transformed from the logit scale 
res$pairwise_tests    # all pairwise comparisons
 contrast                                           odds.ratio      SE  df null z.ratio
 Bacillota_Lytic_Spor / Bacillota_Lytic_NonSpor        14.5040  5.4100 Inf    1   7.170
 Bacillota_Lytic_Spor / Bacillota_Temp_NonSpor         38.2086 18.2000 Inf    1   7.636
 Bacillota_Lytic_Spor / Bacillota_Temp_Spor             2.1500  0.5680 Inf    1   2.900
 Bacillota_Lytic_Spor / OtherPhyla_Lytic_NonSpor       14.5967  2.7900 Inf    1  14.005
 Bacillota_Lytic_Spor / OtherPhyla_Temp_NonSpor        15.8310  3.2200 Inf    1  13.599
 Bacillota_Lytic_NonSpor / Bacillota_Temp_NonSpor       2.6343  1.4700 Inf    1   1.731
 Bacillota_Lytic_NonSpor / Bacillota_Temp_Spor          0.1482  0.0584 Inf    1  -4.846
 Bacillota_Lytic_NonSpor / OtherPhyla_Lytic_NonSpor     1.0064  0.3520 Inf    1   0.018
 Bacillota_Lytic_NonSpor / OtherPhyla_Temp_NonSpor      1.0915  0.3890 Inf    1   0.246
 Bacillota_Temp_NonSpor / Bacillota_Temp_Spor           0.0563  0.0278 Inf    1  -5.830
 Bacillota_Temp_NonSpor / OtherPhyla_Lytic_NonSpor      0.3820  0.1750 Inf    1  -2.097
 Bacillota_Temp_NonSpor / OtherPhyla_Temp_NonSpor       0.4143  0.1920 Inf    1  -1.899
 Bacillota_Temp_Spor / OtherPhyla_Lytic_NonSpor         6.7892  1.5600 Inf    1   8.346
 Bacillota_Temp_Spor / OtherPhyla_Temp_NonSpor          7.3633  1.7600 Inf    1   8.342
 OtherPhyla_Lytic_NonSpor / OtherPhyla_Temp_NonSpor     1.0846  0.1690 Inf    1   0.521
 p.value
  <.0001
  <.0001
  0.0434
  <.0001
  <.0001
  0.5110
  <.0001
  1.0000
  0.9999
  <.0001
  0.2888
  0.4025
  <.0001
  <.0001
  0.9954

P value adjustment: tukey method for comparing a family of 6 estimates 
Tests are performed on the log odds ratio scale 
prob_df <- as.data.frame(res$probabilities)
head(prob_df)
 host_phage_spor                prob          SE  df  asymp.LCL  asymp.UCL
 Bacillota_Lytic_Spor     0.13312693 0.018902074 Inf 0.10023534 0.17471589
 Bacillota_Lytic_NonSpor  0.01047730 0.003474089 Inf 0.00546026 0.02001136
 Bacillota_Temp_NonSpor   0.00400320 0.001786691 Inf 0.00166722 0.00958076
 Bacillota_Temp_Spor      0.06666667 0.012881224 Inf 0.04544214 0.09679919
 OtherPhyla_Lytic_NonSpor 0.01041140 0.001020512 Inf 0.00859006 0.01261400
 OtherPhyla_Temp_NonSpor  0.00960747 0.001142782 Inf 0.00760772 0.01212644

Confidence level used: 0.95 
Intervals are back-transformed from the logit scale 
prob_df$host_phage_spor <- reorder(prob_df$host_phage_spor, prob_df$prob)



fig.meta <- merge(prob_df, meta.cats, by="host_phage_spor", all=TRUE)
pairs_df <- as.data.frame(res$pairwise_tests) %>%
  mutate(
    contrast_char = as.character(contrast),
    p_label = case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01  ~ "**",
      p.value < 0.05  ~ "*",
      TRUE            ~ "ns"
    ),
    group1 = sapply(strsplit(contrast_char, " / "), `[`, 1),
    group2 = sapply(strsplit(contrast_char, " / "), `[`, 2)
  )

pairs_df <- as.data.frame(res$pairwise_tests) %>%
  mutate(
    contrast_char = as.character(contrast),
    p_label = case_when(
      p.value > 0.05            ~ "ns",                      # non-significant
      p.value < 0.001           ~ "p < 0.0001",              # very small p-values
      TRUE                       ~ paste0("p = ", sprintf("%.3f", p.value))  # others
    ),
    group1 = sapply(strsplit(contrast_char, " / "), `[`, 1),
    group2 = sapply(strsplit(contrast_char, " / "), `[`, 2)
  )




pairs_df <- pairs_df %>%
  mutate(
    x_num1 = as.numeric(factor(group1, levels = levels(fig.meta$phage_type))),
    x_num2 = as.numeric(factor(group2, levels = levels(fig.meta$phage_type))),
    span = abs(x_num1 - x_num2),
    x_pos = (x_num1 + x_num2) / 2
  ) %>%
  arrange(span)

# -----------------------------
# 4️⃣ Compute y positions for nested brackets
offset_step <- 0.01

pairs_df <- pairs_df %>%
  rowwise() %>%
  mutate(
    y_base = max(
      fig.meta$asymp.UCL[fig.meta$host_phage_spor %in% c(group1, group2)],
      na.rm = TRUE
    )
  ) %>%
  ungroup() %>%
  arrange(span, desc(p_label)) %>%  # shorter spans lower, ns lower
  mutate(
    y_pos = y_base + (row_number() - 1) * offset_step
  )

# -----------------------------
# 5️⃣ Prepare comparisons list for ggsignif
comparisons_list <- lapply(1:nrow(pairs_df), function(i) {
  c(as.character(pairs_df$group1[i]), as.character(pairs_df$group2[i]))
})

# -----------------------------
# 6️⃣ Plot with dodge and black brackets
pd <- position_dodge(width = 0.5)

tplot <- ggplot(fig.meta, aes(x = host_phage_spor, y = prob, color = lifestyle)) +
  geom_point(size = 3, position = pd) +
  geom_errorbar(aes(ymin = asymp.LCL, ymax = asymp.UCL), width = 0.2, position = pd) +
  ggsignif::geom_signif(
    comparisons = comparisons_list,
    annotations = pairs_df$p_label,
    y_position = pairs_df$y_pos,
    tip_length = 0.02,
    textsize = 3.5,
    color = "black"
  ) +
  ylim(0, max(pairs_df$y_pos + 0.02)) +  # extend y-axis to fit top brackets
   ylab("Probability of 1 or more ParS \n binding sites in phage genome") +
  xlab("") +
  labs(color = "Phage Lifestyle") +
  theme_classic(base_size = 14)+scale_x_discrete(
    labels = c(
      'Lytic_Spor'    = 'Lytic',
      'Temp_Spor'     = 'Temperate',
      'Lytic_NonSpor' = 'Lytic',
      'Temp_NonSpor'  = 'Temperate'
    )
  ) + theme(plot.margin = margin(10, 10, 30, 10)  # large bottom margin for labels
  ) +
  coord_cartesian(clip = "off")

tplot



spor_label <- textGrob(
  "Sporulating Host", gp = gpar(fontsize = 14, fontface = "bold"),
  x = 0.25, y = -0.13, just = "center"
)

nonspor_label <- textGrob(
  "Non-Sporulating Host", gp = gpar(fontsize = 14, fontface = "bold"),
  x = 0.75, y = -0.13, just = "center"
)


tplot_final <- tplot +
  annotation_custom(spor_label) +
  annotation_custom(nonspor_label)

tplot_final

NA
NA
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkoZW1tZWFucykKbGlicmFyeShnZ3NpZ25pZikKbGlicmFyeShoZXJlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShncmlkKQpsaWJyYXJ5KFBOV0NvbG9ycykKCnRoZW1lX2ViIDwtIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB0aGVtZSgKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpCiAgKQoKCgoKdGhlbWVfc2V0KHRoZW1lX2ViKQpwYWwzIDwtIHBud19wYWxldHRlKCJTdW5zZXQiLCAzLCB0eXBlPWMoImRpc2NyZXRlIikpCnBhbDcgPC0gIHBud19wYWxldHRlKCJTdGFyZmlzaCIsIDcsIHR5cGU9YygiZGlzY3JldGUiKSkKCmhlcmU6OmhlcmUoKQoKIyMjIG5vdGUgdGhlIHF1YXJ0eiBzY3JpcHQgbmVlZHMgdG8gYmUgZml4ZWQsIHRoZSBoZWFkZXJzIGFyZSBqYW5reSBhbmQgd3JvbmcgKGhhZCB0byBiZSBtYW51YWxseSBmaXhlZCkKCgoKUGFyUyA8LSByZWFkLmRlbGltMihoZXJlKCIwMl9tb3RpZnMvUGFyUy9kYXRhLzAxX291dF8wMV9pbmRpdl9obW0xX1BhclMudHN2IikpCgoKUGFyUyRxLnZhbHVlPC0gYXMubnVtZXJpYyhQYXJTJHEudmFsdWUpClBhclMkcC52YWx1ZTwtIGFzLm51bWVyaWMoUGFyUyRwLnZhbHVlKQpQYXJTJHNjb3JlPC0gYXMubnVtZXJpYyhQYXJTJHNjb3JlKQoKCgojIyMgcmVtb3ZlIGFueSBkdXBsaWNhdGUgbW90aWZzIChvY2Nhc2lvbmFsbHkgYSBtb3RpZiB3aWxsIGJlIGEgcGFsaW5kcm9taWMgYW5kIGhpdCBib3RoICsgYW5kIC0gc3RyYW5kcykgClBhclMgPC0gUGFyUyAlPiUKICBncm91cF9ieShzZXF1ZW5jZV9uYW1lLCBzdGFydCwgc3RvcCkgJT4lICAjIGdyb3VwIGJ5IGNvb3JkaW5hdGVzCiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gc2NvcmUsIG4gPSAxKSAlPiUgICAgIyBrZWVwIG9ubHkgdGhlIHJvdyB3aXRoIGhpZ2hlc3Qgc2NvcmUKICB1bmdyb3VwKCkKCiMjIyBwaGkzVCBLWTAzMDc4Miwgc3BiZXRhIEFGMDIwNzEzCiMjIHJlZHJvY2sgKGFjdGlubyBwaGFnZSB3aXRoIFBhckFCUykgR1UzMzk0NjcsIHVzZXMgcGFyUyBzaXRlcyBidXQgb2YgYSBkaWZmZXJlbnQgdHlwZSB0aGFuIHNwb3J1bGF0aW9uIFBhclMKCiMjIGNvbWJpbmUgcGhhZ2UgbWV0YWRhdGEgd2l0aCBQYXJTIGhpdHMuIExhYmVsIGFueSBwaGFnZSB0aGF0IGhhZCBubyBQYXJTIGhpdHMgd2l0aCAibm9faGl0IiBpbiBtZXRhZGF0YQppbnBoIDwtIHJlYWQuY3N2KGhlcmUoIjAwX2RhdGEiLCAiaW5waGFyZWRfZGIiLCAiMTRBcHIyMDI1X2tub3duc3BvcmVzdGF0dXMuY3N2IiksIHJvdy5uYW1lcyA9IDEpCmlucGgkbGlmZXN0eWxlIDwtIGlmZWxzZShpbnBoJGxpZmVzdHlsZT09IlRlbXAiLCAiVGVtcGVyYXRlIiwgaW5waCRsaWZlc3R5bGUpCmlucGgkYmFjLmhvc3QgPC0gaWZlbHNlKGlucGgkc3BvcnVsYXRpb249PSJTcG9yIiwgIlNwb3J1bGF0aW5nIEJhY2lsbG90YSIsICJOb25zcG9ydWxhdGluZyBCYWNpbGxvdGEiKSAgIyJBc3Bvcm9nZW5vdXMgQmFjaWxsb3RhIikKaW5waCRiYWMuaG9zdCA8LSBpZmVsc2UoaW5waCRuZXdndGRiX1BoeWx1bT09IkJhY2lsbG90YSIsIGlucGgkYmFjLmhvc3QsICJOb24tQmFjaWxsb3RhIikKCmlucGggPC0gdW5pdGUoaW5waCwgbmljZSwgYygibGlmZXN0eWxlIiwgImJhYy5ob3N0IiksIHNlcD0iIFBoYWdlcyBvZiAiLCByZW1vdmUgPSBGQUxTRSApCgoKbWV0YS5jYXRzIDwtIHVuaXF1ZShzZWxlY3QoaW5waCwgaG9zdF9waGFnZV9zcG9yLCBiYWMuaG9zdCwgbGlmZXN0eWxlLCBuaWNlLCBzcG9ydWxhdGlvbikpCgoKCiNpbnBoIDwtIHNlbGVjdChpbnBoLCBBY2Nlc3Npb24sIEhvc3QsIEdlbm9tZS5MZW5ndGguLmJwLiwgZ3RkYl9mLCBmX3Nwb3IsIG5ld2d0ZGJfUGh5bHVtLCAgaG9zdF9waGFnZV9zcG9yLCBwaGFnZV90eXBlLCBzcG9ydWxhdGlvbiwgbGlmZXN0eWxlLCBuaWNlKQphbGwgPC0gbWVyZ2UoaW5waCwgUGFyUywgYnkueD0iQWNjZXNzaW9uIiwgYnkueT0ic2VxdWVuY2VfbmFtZSIsIGFsbC54PVRSVUUsIGFsbC55PUZBTFNFKQphbGwkbW90aWZfaWRbaXMubmEoYWxsJG1vdGlmX2lkKV0gPC0gIm5vX2hpdCIKCiMjIyBjcmVhdGUgYmluYXJ5IGhpdCBjb2x1bW4gb2YgMSBmb3IgUGFyUyBoaXQsIDAgaWYgbm8gaGl0CmFsbCRoaXQgPC0gaWZlbHNlKGFsbCRtb3RpZl9pZD09IlBhclNfQlN1YiIsIDEsIDApCmFsbCRxLnZhbHVlW2lzLm5hKGFsbCRxLnZhbHVlKV0gPC0gMQoKIyMjIHJlbW92ZSBkdXBsaWNhdGVzIChrZWVwIG9ubHkgcmVmZXJlbmNlIHN0cmFpbnMpCiMjIyBjdXJyZW50bHkgdGhpcyBvbmx5IHdvcmtzIG9uIGJhY2lsbGlvdGEsIDk5LjUlIEFOSSBvdmVyIDEwMCUgYWxpZ25tZW50IHRocmVzaG9sZAojIyByZXN0IG9mIGlucGhhcmVkIGlzIHBlbmRpbmcgCgphbGwgPC0gc3Vic2V0KGFsbCwgYWxsJHJlZj09Im5vdF9ydW4iIHwgYWxsJHJlZj09InJlZl9zZXEiKQpgYGAKCiMjIyB0aHJlc2hvbGQgdGVzdGluZwpgYGB7cn0KCgojIyMgdGhyZXNob2xkIHRlc3RpbmcKCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKCiMgRGVmaW5lIHRocmVzaG9sZHMKdGhyZXNob2xkcyA8LSAxMF5zZXEoLTYsIC0xLCBieSA9IDEpICAgIyBmcm9tIDFlLTYgdG8gMWUtMQp0aHJlc2hvbGRzIDwtIGModGhyZXNob2xkcywgMC4wNSwgMSkgICAgICAjIGFkZCAwLjA1IGV4cGxpY2l0bHkgaWYgZGVzaXJlZApyZXN1bHRzIDwtIGxhcHBseSh0aHJlc2hvbGRzLCBmdW5jdGlvbih0aCkgewogIGFsbCAlPiUKICAgIGdyb3VwX2J5KEFjY2Vzc2lvbiwgaG9zdF9waGFnZV9zcG9yKSAlPiUKICAgIHN1bW1hcmlzZSgKICAgICAgcG9zLlBhclMuaGl0ID0gbWF4KGlmZWxzZSghaXMubmEocS52YWx1ZSkgJiBxLnZhbHVlIDw9IHRoLCAxLCAwKSksCiAgICAgIC5ncm91cHMgPSAiZHJvcCIKICAgICkgJT4lCiAgICBncm91cF9ieShob3N0X3BoYWdlX3Nwb3IpICU+JQogICAgc3VtbWFyaXNlKAogICAgICBQaGFnZV9oYXNfUGFyUyA9IHN1bShwb3MuUGFyUy5oaXQpLAogICAgICB0b3RhbC5waGFnZSA9IG4oKSwKICAgICAgLmdyb3VwcyA9ICJkcm9wIgogICAgKSAlPiUKICAgIG11dGF0ZSgKICAgICAgUGFyUy5wb3MucGVyYyA9IFBoYWdlX2hhc19QYXJTIC8gdG90YWwucGhhZ2UgKiAxMDAsCiAgICAgIHRocmVzaG9sZCA9IHRoCiAgICApCn0pICU+JSBiaW5kX3Jvd3MoKQoKcmVzdWx0cy5maWcgPC0gbWVyZ2UocmVzdWx0cywgbWV0YS5jYXRzLCBieT0iaG9zdF9waGFnZV9zcG9yIikKCiMgUGxvdApwPC0gZ2dwbG90KHJlc3VsdHMuZmlnLCBhZXMoeCA9IHRocmVzaG9sZCwgeSA9IFBhclMucG9zLnBlcmMsIGNvbG9yID0gYmFjLmhvc3QsIGxpbmV0eXBlID0gbGlmZXN0eWxlKSkgKwogIGdlb21fbGluZShzaXplID0gMS4yKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV94X2xvZzEwKCkgKyAgIyBsb2cgc2NhbGUgZm9yIHEtdmFsdWVzCiAgbGFicygKICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgKHEtdmFsdWUpIFRocmVzaG9sZCIsCiAgICB5ID0gIiUgUGhhZ2VzIHdpdGggMSBvciBtb3JlIFBhclMgQmluZGluZyBTaXRlIgogICkgKyBsYWJzKGxpbmV0eXBlID0gIlBoYWdlIExpZmVzdHlsZSIsIGNvbG9yPSJCYWN0ZXJpYWwgSG9zdCIpICtnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxZS00LCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSAjKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQpwICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjEyNSwgMC44MikpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHJldihwYWwzKSkKCgpnZ3NhdmUoaGVyZSgiMDJfbW90aWZzL1BhclMvUVZhbHVlVHJlc2gxZTRfbGluZS5wbmciKSx3aWR0aD03LCBoZWlnaHQ9NykKYGBgCgoKIyMgRmlsdGVyIHcvIHEgdmFsdWUgYW5kIHN1bW1hcml6ZSBkYXRhCmBgYHtyfQoKCiMjIyMgUSBGSUxURVJJTkcKUGFyUy5xdWFsIDwtIGFsbAoKI1BhclMucXVhbCRoaXQgPC0gaWZlbHNlKFBhclMucXVhbCRxLnZhbHVlPDFlLTQsIDEsIDApClBhclMucXVhbCRoaXQgPC0gaWZlbHNlKFBhclMucXVhbCRxLnZhbHVlPDFlLTQsIDEsIDApCgojIyBjcmVhdGUgYmluYXJ5IGxpc3Qgb2YgcGhhZ2VzIHcvIGFuZCB3L291dCBQYXJTIGhpdHMKUGFyUy5wb3MgPC0gUGFyUy5xdWFsICU+JSBncm91cF9ieShBY2Nlc3Npb24sIGhvc3RfcGhhZ2Vfc3BvcikgJT4lCiAgc3VtbWFyaXNlKHRvdGFsLlBhclMuaGl0cyA9IHN1bShoaXQpLCBwb3MuUGFyUy5oaXQgPSBtYXgoaGl0KSwgLmdyb3VwcyA9ICJkcm9wIikgIyU+JSAKUGFyUy5wb3MgJT4lIGNvdW50KGhvc3RfcGhhZ2Vfc3BvcikKCgp3cml0ZS5jc3YoUGFyUy5wb3MsIGhlcmUoIjAyX21vdGlmcy9QYXJTL1BhclNwb3NfMWU0LmNzdiIpKQojIyBjcmVhdGUgYmluYXJ5IGxpc3Qgb2YgcGhhZ2VzIHcvIGFuZCB3L291dCBQYXJTIGhpdHMKUGFyUy5zYW5pdHkgPC0gUGFyUy5xdWFsICU+JSBncm91cF9ieShBY2Nlc3Npb24sIGhvc3RfcGhhZ2Vfc3BvciwgSG9zdCwgRGVzY3JpcHRpb24sIExvd2VzdC5UYXhhLCBHZW51cywgRmFtaWx5LCBHZW5vbWUuTGVuZ3RoLi5icC4sIG1vbEdDLi4uLikgJT4lCiAgc3VtbWFyaXNlKHRvdGFsLlBhclMuaGl0cyA9IHN1bShoaXQpLCBwb3MuUGFyUy5oaXQgPSBtYXgoaGl0KSwgLmdyb3VwcyA9ICJkcm9wIikgIyU+JSAKCm9ubHkucG9zIDwtIHN1YnNldChQYXJTLnNhbml0eSwgUGFyUy5zYW5pdHkkdG90YWwuUGFyUy5oaXRzPjApCgoKUGFyUy5wb3MgJT4lIGNvdW50KGhvc3RfcGhhZ2Vfc3BvcikKClBhclMuc3VtbWFyeSA8LSBQYXJTLnBvcyAlPiUgCiAgZ3JvdXBfYnkoaG9zdF9waGFnZV9zcG9yKSAlPiUgCiAgc3VtbWFyaXNlKAogICAgdG90YWxfUGFyU19oaXRzID0gc3VtKHRvdGFsLlBhclMuaGl0cywgbmEucm0gPSBUUlVFKSwKICAgIFBoYWdlX2hhc19QYXJTID0gc3VtKHBvcy5QYXJTLmhpdCwgbmEucm0gPSBUUlVFKSwKICAgIHRvdGFsLnBoYWdlID0gbigpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgJT4lIAogIG11dGF0ZShQYXJTLnBvcy5wZXJjID0gUGhhZ2VfaGFzX1BhclMgLyB0b3RhbC5waGFnZSAqIDEwMCkKCgpQYXJTLmJpbiA8LSBQYXJTLnBvc1ssYygxLDIsNCldCgoKYGBgCgojIyMjIFBhclMgcG9zaXRpb25hbCBhbmFseXNpcwpgYGB7cn0KCgpQYXJTLmhpdHMgPC0gc3Vic2V0KFBhclMucXVhbCwgUGFyUy5xdWFsJGhpdD09MSkKCiMjIGZpbmQgY2VudGVyIHBoYWdlIGdlbm9tZSAod2hvbGUgZ2Vub21lIC8yKQpQYXJTLmhpdHMkc2VxLm1kcHQgPC0gYXMubnVtZXJpYyhQYXJTLmhpdHMkR2Vub21lLkxlbmd0aC4uYnAuKS8yCgojIyBmaW5kIGNlbnRlciBvZiBtb3RpZiAKUGFyUy5oaXRzJG1vdGlmLm1kcHQgPC0gKFBhclMuaGl0cyRzdG9wICsgUGFyUy5oaXRzJHN0YXJ0ICkvIDIKCiMjIyBzdWJ0cmFjdCBtaWRwb2ludCBtb3RpZiBmcm9tIHNlcXVlbmNlIG1pZHBvaW50IHRvIHNlZSBob3cgZmFyIGF3YXkgdGhleSBhcmUKUGFyUy5oaXRzIDwtIFBhclMuaGl0cyAlPiUKICBtdXRhdGUobWRwdC5hbGlnbiA9IChtb3RpZi5tZHB0IC0gc2VxLm1kcHQpKQoKUGFyUy5oaXRzJG1kcHQuYWxpZ24ua2JwIDwtIFBhclMuaGl0cyRtZHB0LmFsaWduLzEwMDAKCiMjIyMgVE8gR0VUIFJFTEFUSVZFIG1vdGlmIGFsaWdubWVuCgojIFJlbGF0aXZlIHBvc2l0aW9uIGFzIGZyYWN0aW9uIG9mIGdlbm9tZSBsZW5ndGgKIyAoLTAuNSA9IHN0YXJ0LCAwID0gY2VudGVyLCArMC41ID0gZW5kKQpQYXJTLmhpdHMkcmVsLm1kcHQgPC0gKFBhclMuaGl0cyRtb3RpZi5tZHB0IC0gUGFyUy5oaXRzJHNlcS5tZHB0KSAvIFBhclMuaGl0cyRHZW5vbWUuTGVuZ3RoLi5icC4KCgoKIyBSZWxhdGl2ZSBwb3NpdGlvbiBmcm9tIGdlbm9tZSBzdGFydCAoMCB0byAxKQpQYXJTLmhpdHMkcmVsLmZyYWMgPC0gUGFyUy5oaXRzJG1vdGlmLm1kcHQgLyBQYXJTLmhpdHMkR2Vub21lLkxlbmd0aC4uYnAuCgojIE9wdGlvbmFsbHkgY29udmVydCB0byBwZXJjZW50YWdlClBhclMuaGl0cyRyZWwucGVyY2VudCA8LSBQYXJTLmhpdHMkcmVsLmZyYWMgKiAxMDAKCiMjU2V0IHNwZWNpZmljIG9yZGVyIGZvciBiYWN0ZXJpYWwgaG9zdHMgdG8gYXBwZWFyIG9uIGdyYXBocwpQYXJTLmhpdHMkbmljZSA8LSBmYWN0b3IoUGFyUy5oaXRzJG5pY2UsIGxldmVscyA9IGMoJ0x5dGljIFBoYWdlcyBvZiBTcG9ydWxhdGluZyBCYWNpbGxvdGEnLCAnVGVtcGVyYXRlIFBoYWdlcyBvZiBTcG9ydWxhdGluZyBCYWNpbGxvdGEnLCAnTHl0aWMgUGhhZ2VzIG9mIE5vbnNwb3J1bGF0aW5nIEJhY2lsbG90YScsICdUZW1wZXJhdGUgUGhhZ2VzIG9mIE5vbnNwb3J1bGF0aW5nIEJhY2lsbG90YScsICdMeXRpYyBQaGFnZXMgb2YgTm9uLUJhY2lsbG90YScsICdUZW1wZXJhdGUgUGhhZ2VzIG9mIE5vbi1CYWNpbGxvdGEnICksb3JkZXJlZCA9IFRSVUUpCgoKZ2dwbG90KFBhclMuaGl0cywgYWVzKHggPSBtZHB0LmFsaWduLmticCwgZmlsbCA9IG5pY2UpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA1LCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgtOTAsIDkwLCBieSA9IDMwKSkgKwogIGxhYnMoeCA9ICJNb3RpZiBkaXN0YW5jZSAoa2IpIGZyb20gY2VudGVyIG9mIHBoYWdlIGdlbm9tZSIsIHkgPSAiTW90aWYgY291bnQiKSArCiAgZmFjZXRfd3JhcCh+IG5pY2UsIG5jb2wgPSAyKSsgZ2d0aXRsZSgiQWJzb2x1dGUgUG9zaXRpb24gb2YgUGFyUyBNb3RpZiBvbiBQaGFnZSBHZW5vbWVzIikgKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsNykKCgpnZ3NhdmUoaGVyZSgiMDJfbW90aWZzL1BhclMvZmlncy9BYnNvbHV0ZVBhclNwb3NpdGlvbl8xZTQucG5nIiksIHdpZHRoPTcsIGhlaWdodD03KQoKZ2dwbG90KFBhclMuaGl0cywgYWVzKHggPSByZWwubWRwdCwgZmlsbCA9IG5pY2UpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjA1LCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgtMC41LCAwLjUsIGJ5ID0gMC4yNSkpICsKICBsYWJzKHggPSAiTW90aWYgcG9zaXRpb24gcmVsYXRpdmUgdG8gY2VudGVyIG9mIHBoYWdlIGdlbm9tZSIsIHkgPSAiTW90aWYgY291bnQiKSArCiAgZmFjZXRfd3JhcCh+IG5pY2UsIG5jb2wgPSAyKSArIGdndGl0bGUoIlJlbGF0aXZlIFBvc2l0aW9uIG9mIFBhclMgTW90aWYgb24gUGhhZ2UgR2Vub21lcyIpKyB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbDcpCgoKZ2dzYXZlKGhlcmUoIjAyX21vdGlmcy9QYXJTL2ZpZ3MvUmVsYXRpdmVQYXJTcG9zaXRpb25fMWU0LnBuZyIpLCB3aWR0aD03LCBoZWlnaHQ9NykKYGBgCiMjIyBFbnJpY2htZW50CmBgYHtyfQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnBhaXJ3aXNlX2Zpc2hlcl9ib3RoX2RpcmVjdGlvbnMgPC0gZnVuY3Rpb24oZGYsIGdyb3VwX2NvbCwgaGl0c19jb2wsIHRvdGFsX2NvbCwgcC5hZGp1c3QubWV0aG9kID0gIkJIIikgewogIAogIGdyb3VwcyA8LSB1bmlxdWUoZGZbW2dyb3VwX2NvbF1dKQogIGNvbWJzIDwtIGNvbWJuKGdyb3VwcywgMiwgc2ltcGxpZnkgPSBGQUxTRSkKICAKICByZXN1bHRzIDwtIGRhdGEuZnJhbWUoCiAgICBncm91cDEgPSBjaGFyYWN0ZXIoKSwKICAgIGdyb3VwMiA9IGNoYXJhY3RlcigpLAogICAgb2Rkc19yYXRpbyA9IG51bWVyaWMoKSwKICAgIGNvbmZfbG93ID0gbnVtZXJpYygpLAogICAgY29uZl9oaWdoID0gbnVtZXJpYygpLAogICAgcC52YWx1ZSA9IG51bWVyaWMoKSwKICAgIHAuYWRqID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQogIAogIHB2YWxzIDwtIG51bWVyaWMoKQogIAogIGZvciAoYyBpbiBjb21icykgewogICAgZzEgPC0gZGZbZGZbW2dyb3VwX2NvbF1dID09IGNbMV0sIF0KICAgIGcyIDwtIGRmW2RmW1tncm91cF9jb2xdXSA9PSBjWzJdLCBdCiAgICAKICAgIHRhYiA8LSBtYXRyaXgoYygKICAgICAgZzFbW2hpdHNfY29sXV0sIGcxW1t0b3RhbF9jb2xdXSAtIGcxW1toaXRzX2NvbF1dLAogICAgICBnMltbaGl0c19jb2xdXSwgZzJbW3RvdGFsX2NvbF1dIC0gZzJbW2hpdHNfY29sXV0KICAgICksIG5yb3cgPSAyLCBieXJvdyA9IFRSVUUpCiAgICAKICAgIHJvd25hbWVzKHRhYikgPC0gYyhjWzFdLCBjWzJdKQogICAgY29sbmFtZXModGFiKSA8LSBjKCJQcmVzZW50IiwgIkFic2VudCIpCiAgICAKICAgIGZ0IDwtIGZpc2hlci50ZXN0KHRhYikKICAgIAogICAgIyBncm91cDEgdnMgZ3JvdXAyCiAgICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoCiAgICAgIGdyb3VwMSA9IGNbMV0sCiAgICAgIGdyb3VwMiA9IGNbMl0sCiAgICAgIG9kZHNfcmF0aW8gPSBmdCRlc3RpbWF0ZSwKICAgICAgY29uZl9sb3cgPSBmdCRjb25mLmludFsxXSwKICAgICAgY29uZl9oaWdoID0gZnQkY29uZi5pbnRbMl0sCiAgICAgIHAudmFsdWUgPSBmdCRwLnZhbHVlLAogICAgICBwLmFkaiA9IE5BCiAgICApKQogICAgCiAgICAjIGdyb3VwMiB2cyBncm91cDEgKGludmVyc2UgT1IpCiAgICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoCiAgICAgIGdyb3VwMSA9IGNbMl0sCiAgICAgIGdyb3VwMiA9IGNbMV0sCiAgICAgIG9kZHNfcmF0aW8gPSAxIC8gZnQkZXN0aW1hdGUsCiAgICAgIGNvbmZfbG93ID0gMSAvIGZ0JGNvbmYuaW50WzJdLAogICAgICBjb25mX2hpZ2ggPSAxIC8gZnQkY29uZi5pbnRbMV0sCiAgICAgIHAudmFsdWUgPSBmdCRwLnZhbHVlLAogICAgICBwLmFkaiA9IE5BCiAgICApKQogICAgCiAgICBwdmFscyA8LSBjKHB2YWxzLCBmdCRwLnZhbHVlLCBmdCRwLnZhbHVlKQogIH0KICAKICByZXN1bHRzJHAuYWRqIDwtIHAuYWRqdXN0KHB2YWxzLCBtZXRob2QgPSBwLmFkanVzdC5tZXRob2QpCiAgcmV0dXJuKHJlc3VsdHMpCn0KCmBgYAoKYGBge3J9CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBDb21wdXRlIEZpc2hlciB0ZXN0cwp0IDwtIHBhaXJ3aXNlX2Zpc2hlcl9ib3RoX2RpcmVjdGlvbnMoCiAgZGYgPSBQYXJTLnN1bW1hcnksCiAgZ3JvdXBfY29sID0gImhvc3RfcGhhZ2Vfc3BvciIsCiAgaGl0c19jb2wgPSAiUGhhZ2VfaGFzX1BhclMiLAogIHRvdGFsX2NvbCA9ICJ0b3RhbC5waGFnZSIsCiAgcC5hZGp1c3QubWV0aG9kID0gIkJIIgopCgojIFNwZWNpZnkgcGFpcnMgb2YgaW50ZXJlc3QgKHdpdGggZGlyZWN0aW9uKQpzZWxlY3RlZF9wYWlycyA8LSBsaXN0KAogIGMoIk90aGVyUGh5bGFfTHl0aWNfTm9uU3BvciIsICJPdGhlclBoeWxhX1RlbXBfTm9uU3BvciIpLCAKICBjKCJCYWNpbGxvdGFfTHl0aWNfTm9uU3BvciIsICJCYWNpbGxvdGFfVGVtcF9Ob25TcG9yIiksCiAgYygiQmFjaWxsb3RhX1RlbXBfU3BvciIsICJCYWNpbGxvdGFfVGVtcF9Ob25TcG9yIiksCiAgYygiQmFjaWxsb3RhX0x5dGljX1Nwb3IiLCAiT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yIiksCiAgYygiQmFjaWxsb3RhX0x5dGljX1Nwb3IiLCAiQmFjaWxsb3RhX0x5dGljX05vblNwb3IiKSwKICBjKCJCYWNpbGxvdGFfTHl0aWNfU3BvciIsICJCYWNpbGxvdGFfVGVtcF9TcG9yIikKKQoKcGFpcnNfZGYgPC0gZG8uY2FsbChyYmluZCwgbGFwcGx5KHNlbGVjdGVkX3BhaXJzLCBmdW5jdGlvbih4KSB7CiAgZGF0YS5mcmFtZShncm91cDEgPSB4WzFdLCBncm91cDIgPSB4WzJdLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCn0pKQoKCgpwYWlyd2lzZV9yZXNfc3Vic2V0IDwtIHQgJT4lCiAgc2VtaV9qb2luKHBhaXJzX2RmLCBieSA9IGMoImdyb3VwMSIsICJncm91cDIiKSkgCgoKCnBhaXJ3aXNlX3Jlc19zdWJzZXQgPC0gcGFpcndpc2VfcmVzX3N1YnNldCAlPiUKICBtdXRhdGUoCiAgICBsYWJlbF9jb21iaW5lZCA9IGlmZWxzZSgKICAgICAgcC5hZGogPCAwLjA1LAogICAgICBpZmVsc2UoCiAgICAgICAgcC5hZGogPCAwLjAwMDEsCiAgICAgICAgcGFzdGUwKCJPUj0iLCByb3VuZChvZGRzX3JhdGlvLCAyKSwgIiwgcDwwLjAwMDEiKSwKICAgICAgICBwYXN0ZTAoIk9SPSIsIHJvdW5kKG9kZHNfcmF0aW8sIDIpLCAiLCBwPSIsIHNpZ25pZihwLmFkaiwgMykpCiAgICAgICksCiAgICAgICJuLnMuIgogICAgKQogICkKCgoKYGBgCgpgYGB7cn0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIEZhY3RvciBvcmRlcgpsZXZlbHNfb3JkZXIgPC0gYygKICAnQmFjaWxsb3RhX0x5dGljX1Nwb3InLAogICdCYWNpbGxvdGFfVGVtcF9TcG9yJywKICAnQmFjaWxsb3RhX0x5dGljX05vblNwb3InLAogICdCYWNpbGxvdGFfVGVtcF9Ob25TcG9yJywKICAnT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yJywKICAnT3RoZXJQaHlsYV9UZW1wX05vblNwb3InCikKCgpkZl9hbGwgPC0gUGFyUy5wb3MKCiMgU2FtcGxlIHNpemVzCm5fY291bnRzIDwtIGRmX2FsbCAlPiUKICBncm91cF9ieShob3N0X3BoYWdlX3Nwb3IpICU+JQogIHN1bW1hcmlzZSgKICAgIHBvc2l0aXZlID0gc3VtKHBvcy5QYXJTLmhpdCA9PSAxLCBuYS5ybSA9IFRSVUUpLAogICAgdG90YWwgICAgPSBuKCksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBtdXRhdGUoCiAgICBwZXJjICA9IHJvdW5kKDEwMCAqIHBvc2l0aXZlIC8gdG90YWwsIDEpLAogICAgbGFiZWwgPSBwYXN0ZTAocG9zaXRpdmUsICIvIiwgdG90YWwsICIgKCIsIHBlcmMsICIlKSIpCiAgKQoKIyBCYXNlIHBsb3QKcGQgPC0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4yNSwgaGVpZ2h0ID0gMC4wNSkKCnZwIDwtIGdncGxvdChkZl9hbGwsIGFlcyh4ID0gaG9zdF9waGFnZV9zcG9yLCB5ID0gcG9zLlBhclMuaGl0LCBjb2xvciA9IGhvc3RfcGhhZ2Vfc3BvcikpICsKICBnZW9tX3BvaW50KAogICAgYWVzKGZpbGwgPSBob3N0X3BoYWdlX3Nwb3IpLAogICAgcG9zaXRpb24gPSBwZCwKICAgIHNpemUgPSAyLAogICAgYWxwaGEgPSAwLjYsCiAgICBzaGFwZSA9IDIxLAogICAgY29sb3IgPSAiYmxhY2siLAogICAgc3Ryb2tlID0gMC4zCiAgKSArCiAgc2NhbGVfeV9jb250aW51b3VzKAogICAgI2xpbWl0cyA9IGMoLTAuNSwgMTApLCAgIyBtYWtlIHJvb20gZm9yIGJhcnMgYWJvdmUKICAgIGJyZWFrcyA9IGMoMCwgMSksCiAgICBsYWJlbHMgPSBjKCJBYnNlbnQiLCAiUHJlc2VudCIpCiAgKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSBsZXZlbHNfb3JkZXIsIGRyb3AgPSBGQUxTRSwgbGFiZWxzID0gYygKICAgICAgJ0JhY2lsbG90YV9MeXRpY19TcG9yJyAgICA9ICdMeXRpYycsCiAgICAgICdCYWNpbGxvdGFfVGVtcF9TcG9yJyAgICAgPSAnVGVtcGVyYXRlJywKICAgICAgJ0JhY2lsbG90YV9MeXRpY19Ob25TcG9yJyA9ICdMeXRpYycsCiAgICAgICdCYWNpbGxvdGFfVGVtcF9Ob25TcG9yJyAgPSAnVGVtcGVyYXRlJywKICAgICAgJ090aGVyUGh5bGFfTHl0aWNfTm9uU3BvcicgPSAnTHl0aWMnLAogICAgICAnT3RoZXJQaHlsYV9UZW1wX05vblNwb3InICA9ICdUZW1wZXJhdGUnCiAgICApKSArCiAgY29vcmRfY2FydGVzaWFuKGNsaXAgPSAib2ZmIikgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMTAsIDEwLCAzMCwgMTApCiAgKSArCiAgeGxhYigiIikgKyB5bGFiKCJQcmVzZW5jZSBvZiBQYXJTIGluIHBoYWdlIGdlbm9tZXMiKSArCiAgZ2VvbV90ZXh0KAogICAgZGF0YSA9IG5fY291bnRzLAogICAgYWVzKHggPSBob3N0X3BoYWdlX3Nwb3IsIHkgPSAtMC4zLCBsYWJlbCA9IHBhc3RlMCgibj0iLCBsYWJlbCkpLAogICAgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICAgIHNpemUgPSA0CiAgKQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnNwb3JfbGFiZWwgICAgPC0gdGV4dEdyb2IoIlNwb3J1bGF0aW5nIEJhY2lsbGlvdGEgSG9zdCIsIGdwID0gZ3Bhcihmb250c2l6ZSA9IDE0LCBmb250ZmFjZSA9ICJib2xkIiksIHggPSAxLzYsIHkgPSAtMC4wOCwganVzdCA9ICJjZW50ZXIiKQpub25zcG9yX2xhYmVsIDwtIHRleHRHcm9iKCJOb24tU3BvcnVsYXRpbmcgQmFjaWxsaW90YSBIb3N0IiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTQsIGZvbnRmYWNlID0gImJvbGQiKSwgeCA9IDMvNiwgeSA9IC0wLjA4LCBqdXN0ID0gImNlbnRlciIpCm90aGVyX2xhYmVsICAgPC0gdGV4dEdyb2IoIk5vbi1CYWNpbGxpb3RhIEhvc3QiLCBncCA9IGdwYXIoZm9udHNpemUgPSAxNCwgZm9udGZhY2UgPSAiYm9sZCIpLCB4ID0gNS82LCB5ID0gLTAuMDgsIGp1c3QgPSAiY2VudGVyIikKCnZwIDwtIHZwICsgYW5ub3RhdGlvbl9jdXN0b20oc3Bvcl9sYWJlbCkgKyBhbm5vdGF0aW9uX2N1c3RvbShub25zcG9yX2xhYmVsKSArIGFubm90YXRpb25fY3VzdG9tKG90aGVyX2xhYmVsKQoKCnBhaXJ3aXNlX3Jlc19zdWJzZXQkZ3JvdXAxIDwtIGZhY3RvcihwYWlyd2lzZV9yZXNfc3Vic2V0JGdyb3VwMSwgbGV2ZWxzID0gbGV2ZWxzX29yZGVyLCBvcmRlcmVkID0gVFJVRSkKcGFpcndpc2VfcmVzX3N1YnNldCA8LSBwYWlyd2lzZV9yZXNfc3Vic2V0ICU+JQogIGFycmFuZ2UoZmFjdG9yKGdyb3VwMSwgbGV2ZWxzID0gbGV2ZWxzX29yZGVyKSwKICAgICAgICAgIGZhY3Rvcihncm91cDIsIGxldmVscyA9IGxldmVsc19vcmRlcikpICU+JQogIG11dGF0ZSh5LnBvc2l0aW9uID0gbWF4KGRmX2FsbCRwb3MuUGFyUy5oaXQsIG5hLnJtID0gVFJVRSkgKyAKICAgICAgICAgICAwLjUgKyAobnJvdyguKSAtIHJvd19udW1iZXIoKSkgKiAwLjMpCgoKCiMgQWRkIHNpZ25pZmljYW5jZSBiYXJzCnZwX2Zpc2hlciA8LSB2cCArCiAgc3RhdF9wdmFsdWVfbWFudWFsKAogICAgcGFpcndpc2VfcmVzX3N1YnNldCwKICAgIGxhYmVsID0gImxhYmVsX2NvbWJpbmVkIiwKICAgIGhpZGUubnMgPSBGQUxTRSwKICAgIHRpcC5sZW5ndGggPSAwLjAyLAogICAgc2l6ZSA9IDMuNQogICkKCiMgU2hvdyBwbG90CnZwX2Zpc2hlcisgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsNykKCgpnZ3NhdmUoaGVyZSgiMDJfbW90aWZzL1BhclMvZmlncy9QYXJTX0VucmljaG1lbnRfMWU0LnBuZyIpLCB3aWR0aD03LCBoZWlnaHQ9NykKCmBgYAoKIyMjIyMgS3J1c2thbApgYGB7cn0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIENvdW50IHBvc2l0aXZlcyBhbmQgdG90YWxzIHBlciBncm91cApuX2NvdW50cyA8LSBkZl9hbGwgJT4lCiAgZ3JvdXBfYnkoaG9zdF9waGFnZV9zcG9yKSAlPiUKICBzdW1tYXJpc2UoCiAgICBwb3NpdGl2ZSA9IHN1bSh0b3RhbC5QYXJTLmhpdHMgPiAwLCBuYS5ybSA9IFRSVUUpLAogICAgdG90YWwgICAgPSBuKCksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBtdXRhdGUoCiAgICBwZXJjICA9IHJvdW5kKDEwMCAqIHBvc2l0aXZlIC8gdG90YWwsIDEpLAogICAgbGFiZWwgPSBwYXN0ZTAocG9zaXRpdmUsICIvIiwgdG90YWwsICIgKCIsIHBlcmMsICIlKSIpCiAgKQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQprcnVza2FsX3JlcyA8LSBrcnVza2FsLnRlc3QodG90YWwuUGFyUy5oaXRzIH4gaG9zdF9waGFnZV9zcG9yLCBkYXRhID0gZGZfYWxsKQojcHJpbnQoa3J1c2thbF9yZXMpCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgRGVmaW5lIGNvbXBhcmlzb25zCmNvbXBhcmlzb25zX2xpc3QgPC0gY29tYm4odW5pcXVlKGRmX2FsbCRob3N0X3BoYWdlX3Nwb3IpLCAyLCBzaW1wbGlmeSA9IEZBTFNFKQoKIyBCYXIgcGxhY2VtZW50IHNldHRpbmdzCm9mZnNldCA8LSAxICAgICAjIHB1c2ggYmFycyBoaWdoZXIgdGhhbiBtYXggZGF0YQp5X3N0ZXAgPC0gMS41ICAgIyB2ZXJ0aWNhbCBzcGFjaW5nCnlfc3RhcnQgPC0gbWF4KGRmX2FsbCR0b3RhbC5QYXJTLmhpdHMsIG5hLnJtID0gVFJVRSkgKyBvZmZzZXQKCiMgQ29tcHV0ZSByYXcgcC12YWx1ZXMKcGFpcndpc2VfcmVzIDwtIGxhcHBseShzZXFfYWxvbmcoY29tcGFyaXNvbnNfbGlzdCksIGZ1bmN0aW9uKGkpewogIGNvbXAgPC0gY29tcGFyaXNvbnNfbGlzdFtbaV1dCiAgeCA8LSBkZl9hbGwkdG90YWwuUGFyUy5oaXRzW2RmX2FsbCRob3N0X3BoYWdlX3Nwb3IgPT0gY29tcFsxXV0KICB5IDwtIGRmX2FsbCR0b3RhbC5QYXJTLmhpdHNbZGZfYWxsJGhvc3RfcGhhZ2Vfc3BvciA9PSBjb21wWzJdXQogIHd0IDwtIHdpbGNveC50ZXN0KHgsIHkpCiAgZGF0YS5mcmFtZSgKICAgIGdyb3VwMSA9IGNvbXBbMV0sCiAgICBncm91cDIgPSBjb21wWzJdLAogICAgcC52YWx1ZSA9IHd0JHAudmFsdWUsCiAgICB5LnBvc2l0aW9uID0geV9zdGFydCArIChpIC0gMSkgKiB5X3N0ZXAKICApCn0pICU+JSBiaW5kX3Jvd3MoKQoKIyBBZGp1c3QgcC12YWx1ZXMgKEJIKQpwYWlyd2lzZV9yZXMkcC5hZGogPC0gcC5hZGp1c3QocGFpcndpc2VfcmVzJHAudmFsdWUsIG1ldGhvZCA9ICJCSCIpCgojIExhYmVsIGZvcm1hdHRpbmcKcGFpcndpc2VfcmVzIDwtIHBhaXJ3aXNlX3JlcyAlPiUKICBtdXRhdGUoCiAgICBsYWJlbCA9IGNhc2Vfd2hlbigKICAgICAgcC5hZGogPCAwLjAwMDEgfiAicCA8IDAuMDAwMSIsCiAgICAgIHAuYWRqIDwgMC4wNSAgIH4gcGFzdGUwKCJwID0gIiwgc2lnbmlmKHAuYWRqLCAzKSksCiAgICAgIFRSVUUgICAgICAgICAgIH4gIm4ucy4iCiAgICApCiAgKQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpsZXZlbHNfb3JkZXIgPC0gYygKICAnQmFjaWxsb3RhX0x5dGljX1Nwb3InLAogICdCYWNpbGxvdGFfVGVtcF9TcG9yJywKICAnQmFjaWxsb3RhX0x5dGljX05vblNwb3InLAogICdCYWNpbGxvdGFfVGVtcF9Ob25TcG9yJywKICAnT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yJywKICAnT3RoZXJQaHlsYV9UZW1wX05vblNwb3InCikKCgoKYGBgCgp2aXN1YWxpemUgCgpgYGB7cn0KCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShyc3RhdGl4KQoKIyAtLS0gRmFjdG9yIHNldHVwIC0tLQpsZXZlbHNfb3JkZXIgPC0gYygKICAnQmFjaWxsb3RhX0x5dGljX1Nwb3InLAogICdCYWNpbGxvdGFfVGVtcF9TcG9yJywKICAnQmFjaWxsb3RhX0x5dGljX05vblNwb3InLAogICdCYWNpbGxvdGFfVGVtcF9Ob25TcG9yJywKICAnT3RoZXJQaHlsYV9MeXRpY19Ob25TcG9yJywKICAnT3RoZXJQaHlsYV9UZW1wX05vblNwb3InCikKCmRmX2FsbCA8LSBQYXJTLnBvcyAlPiUKICBtdXRhdGUoaG9zdF9waGFnZV9zcG9yID0gZmFjdG9yKGhvc3RfcGhhZ2Vfc3BvciwgbGV2ZWxzID0gbGV2ZWxzX29yZGVyKSkKCmRmX3Zpb2xpbiA8LSBkZl9hbGwgJT4lCiAgZmlsdGVyKHRvdGFsLlBhclMuaGl0cyA+IDApCgojIC0tLSBuIGNvdW50cyAtLS0Kbl9jb3VudHMgPC0gZGZfYWxsICU+JQogIGdyb3VwX2J5KGhvc3RfcGhhZ2Vfc3BvcikgJT4lCiAgc3VtbWFyaXNlKAogICAgcG9zaXRpdmUgPSBzdW0odG90YWwuUGFyUy5oaXRzID4gMCwgbmEucm0gPSBUUlVFKSwKICAgIHRvdGFsICAgID0gbigpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkgJT4lCiAgbXV0YXRlKAogICAgcGVyYyAgPSByb3VuZCgxMDAgKiBwb3NpdGl2ZSAvIHRvdGFsLCAxKSwKICAgIGxhYmVsID0gcGFzdGUwKHBvc2l0aXZlLCAiLyIsIHRvdGFsLCAiICgiLCBwZXJjLCAiJSkiKQogICkKCiMgLS0tIFN0YXRzIC0tLQojIEtydXNrYWzigJNXYWxsaXMKa3dfcmVzIDwtIGtydXNrYWxfdGVzdChkZl9hbGwsIHRvdGFsLlBhclMuaGl0cyB+IGhvc3RfcGhhZ2Vfc3BvcikKCiMgUGFpcndpc2UgV2lsY294b24gd2l0aCBCSCBjb3JyZWN0aW9uCndpbGNfdGVzdCA8LSBkZl9hbGwgJT4lCiAgcGFpcndpc2Vfd2lsY294X3Rlc3QodG90YWwuUGFyUy5oaXRzIH4gaG9zdF9waGFnZV9zcG9yLAogICAgICAgICAgICAgICAgICAgICAgIHAuYWRqdXN0Lm1ldGhvZCA9ICJCSCIpICMlPiUKCndpbGNfdGVzdCA8LSB3aWxjX3Rlc3QgJT4lIHVuaXRlKGNvbCA9ICJ0ZXN0IiwgYygiZ3JvdXAxIiwgImdyb3VwMiIpLCByZW1vdmUgPSBGQUxTRSwgc2VwID0gIl8iKQoKCiMjIyBzdWJzZXQgdGVzdHMgeW91IHdhbnQgdG8gc2hvdwp3aWxjX3Rlc3QgPC0gc3Vic2V0KHdpbGNfdGVzdCwgd2lsY190ZXN0JHRlc3Q9PSJCYWNpbGxvdGFfTHl0aWNfU3Bvcl9CYWNpbGxvdGFfVGVtcF9TcG9yIiB8CiAgICAgICAgICAgICAgICAgICAgICB3aWxjX3Rlc3QkdGVzdD09IkJhY2lsbG90YV9MeXRpY19TcG9yX0JhY2lsbG90YV9MeXRpY19Ob25TcG9yIiB8CiAgICAgICAgICAgICAgICAgICAgICB3aWxjX3Rlc3QkdGVzdD09IkJhY2lsbG90YV9MeXRpY19TcG9yX090aGVyUGh5bGFfTHl0aWNfTm9uU3BvciIgfAogICAgICAgICAgICAgICAgICAgICAgd2lsY190ZXN0JHRlc3Q9PSJCYWNpbGxvdGFfVGVtcF9TcG9yX0JhY2lsbG90YV9UZW1wX05vblNwb3IiIHwKICAgICAgICAgICAgICAgICAgICAgIHdpbGNfdGVzdCR0ZXN0PT0iQmFjaWxsb3RhX1RlbXBfU3Bvcl9PdGhlclBoeWxhX1RlbXBfTm9uU3BvciIgfAogICAgICAgICAgICAgICAgICAgICAgd2lsY190ZXN0JHRlc3Q9PSJCYWNpbGxvdGFfTHl0aWNfTm9uU3Bvcl9CYWNpbGxvdGFfVGVtcF9Ob25TcG9yIiB8CiAgICAgICAgICAgICAgICAgICAgICB3aWxjX3Rlc3QkdGVzdD09Ik90aGVyUGh5bGFfTHl0aWNfTm9uU3Bvcl9PdGhlclBoeWxhX1RlbXBfTm9uU3BvciIpCgoKCm1heF95IDwtIG1heChkZl9hbGwkdG90YWwuUGFyUy5oaXRzLCBuYS5ybSA9IFRSVUUpCnN0ZXAgPC0gLjg1CndpbGNfdGVzdCA8LSB3aWxjX3Rlc3QgJT4lCiAgbXV0YXRlKHkucG9zaXRpb24gPSBtYXhfeSArIHJldihzZXEoMSwgbnJvdyguKSkpKnN0ZXApCgoKIyBNYWtlIGxhYmVsOiBPUiBub3QgcmVsZXZhbnQgaGVyZSAoV2lsY294b24pLCBzbyBwIG9ubHkKd2lsY190ZXN0IDwtIHdpbGNfdGVzdCAlPiUKICBtdXRhdGUoCiAgICBwX2xhYmVsID0gaWZlbHNlKHAuYWRqIDwgMC4wMDAxLCAicCA8IDAuMDAwMSIsCiAgICAgICAgICAgICAgaWZlbHNlKHAuYWRqIDwgMC4wNSwgcGFzdGUwKCJwPSIsIHNpZ25pZihwLmFkaiwgMykpLCAibi5zLiIpKQogICkKCgoKICAKIyAtLS0gUGxvdCAtLS0KcGQgPC0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4yNSwgaGVpZ2h0ID0gMC4yNSkKCnZwIDwtIGdncGxvdChkZl9hbGwsIGFlcyh4ID0gaG9zdF9waGFnZV9zcG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHRvdGFsLlBhclMuaGl0cywKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gaG9zdF9waGFnZV9zcG9yKSkgKwogICMgVmlvbGluIGZvciBwb3NpdGl2ZXMKICBnZW9tX3Zpb2xpbigKICAgIGRhdGEgPSBkZl92aW9saW4sCiAgICBpbmhlcml0LmFlcyA9IFRSVUUsCiAgICBjb2xvciA9ICJibGFjayIsCiAgICBmaWxsID0gTkEsCiAgICBzY2FsZSA9ICJ3aWR0aCIsCiAgICB3aWR0aCA9IDAuOCwKICAgIHRyaW0gPSBUUlVFCiAgKSArCiAgIyBKaXR0ZXJlZCBwb2ludHMKICBnZW9tX3BvaW50KAogICAgYWVzKGZpbGwgPSBob3N0X3BoYWdlX3Nwb3IpLAogICAgcG9zaXRpb24gPSBwZCwKICAgIHNpemUgPSAyLAogICAgYWxwaGEgPSAwLjUsCiAgICBzaGFwZSA9IDIxLAogICAgY29sb3IgPSAiYmxhY2siLAogICAgc3Ryb2tlID0gMC4zCiAgKSArCiAgIyBUb3RhbCBuCiAgZ2VvbV90ZXh0KAogIGRhdGEgPSBuX2NvdW50cywKICBhZXMoCiAgICB4ID0gaG9zdF9waGFnZV9zcG9yLAogICAgeSA9IC0wLjc1LCAgICMgc2xpZ2h0bHkgYmVsb3cgMDsgYWRqdXN0IGFzIG5lZWRlZAogICAgbGFiZWwgPSBsYWJlbAogICksCiAgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICBzaXplID0gMywKICB2anVzdCA9IDEgICMgYWxpZ24gdGV4dCBhYm92ZSB0aGUgeSBwb3NpdGlvbgopICsKICAjIEFkZCBzaWduaWZpY2FuY2UgYmFycwogIHN0YXRfcHZhbHVlX21hbnVhbCgKICAgIHdpbGNfdGVzdCwKICAgIGxhYmVsID0gInBfbGFiZWwiLAogICAgaGlkZS5ucyA9IEZBTFNFLAogICAgdGlwLmxlbmd0aCA9IDAuMDIsCiAgICBzaXplID0gMy41CiAgKSArCiAgIyBBeGlzIG9yZGVyCiAgc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSBsZXZlbHNfb3JkZXIsIGRyb3AgPSBGQUxTRSwgbGFiZWxzID0gYygKICAgICAgJ0JhY2lsbG90YV9MeXRpY19TcG9yJyAgICA9ICdMeXRpYycsCiAgICAgICdCYWNpbGxvdGFfVGVtcF9TcG9yJyAgICAgPSAnVGVtcGVyYXRlJywKICAgICAgJ0JhY2lsbG90YV9MeXRpY19Ob25TcG9yJyA9ICdMeXRpYycsCiAgICAgICdCYWNpbGxvdGFfVGVtcF9Ob25TcG9yJyAgPSAnVGVtcGVyYXRlJywKICAgICAgJ090aGVyUGh5bGFfTHl0aWNfTm9uU3BvcicgPSAnTHl0aWMnLAogICAgICAnT3RoZXJQaHlsYV9UZW1wX05vblNwb3InICA9ICdUZW1wZXJhdGUnCiAgICApKSArCiAgIyBHaXZlIHJvb20gZm9yIG4gbGFiZWxzIGFuZCBiYXJzCiAgI2V4cGFuZF9saW1pdHMoeSA9IG1heChkZl9hbGwkdG90YWwuUGFyUy5oaXRzLCBuYS5ybSA9IFRSVUUpICogNCkgKwogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMTAsIDEwLCAzMCwgMTApKSArCiAgeGxhYigiIikgKyBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxMi41LDIpLCAgYnJlYWtzPWMoMCwyLDQsNiw4KSkKI3ZwICMrIGV4cGFuZF9saW1pdHMoeSA9IGMoLTEsIG1heChkZl9hbGwkdG90YWwuUGFyUy5oaXRzKSAqIDEuMikpCgoKc3Bvcl9sYWJlbCAgICA8LSB0ZXh0R3JvYigiU3BvcnVsYXRpbmcgQmFjaWxsaW90YSBIb3N0IiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTQsIGZvbnRmYWNlID0gImJvbGQiKSwgeCA9IDEvNiwgeSA9IC0wLjA4LCBqdXN0ID0gImNlbnRlciIpCm5vbnNwb3JfbGFiZWwgPC0gdGV4dEdyb2IoIk5vbi1TcG9ydWxhdGluZyBCYWNpbGxpb3RhIEhvc3QiLCBncCA9IGdwYXIoZm9udHNpemUgPSAxNCwgZm9udGZhY2UgPSAiYm9sZCIpLCB4ID0gMy82LCB5ID0gLTAuMDgsIGp1c3QgPSAiY2VudGVyIikKb3RoZXJfbGFiZWwgICA8LSB0ZXh0R3JvYigiTm9uLUJhY2lsbGlvdGEgSG9zdCIsIGdwID0gZ3Bhcihmb250c2l6ZSA9IDE0LCBmb250ZmFjZSA9ICJib2xkIiksIHggPSA1LzYsIHkgPSAtMC4wOCwganVzdCA9ICJjZW50ZXIiKQoKdnAua3J1c2sgPC0gdnAgKyBhbm5vdGF0aW9uX2N1c3RvbShzcG9yX2xhYmVsKSArIGFubm90YXRpb25fY3VzdG9tKG5vbnNwb3JfbGFiZWwpICsgYW5ub3RhdGlvbl9jdXN0b20ob3RoZXJfbGFiZWwpKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWw3KQoKCnZwLmtydXNrCgpnZ3NhdmUoaGVyZSgiMDJfbW90aWZzL1BhclMvZmlncy9QYXJTX0hpdHNfS3J1c2thbF8xZTQucG5nIiksIHdpZHRoPTcsIGhlaWdodD03KQpgYGAKCgoKCiMjIyMgRU1NRUFOUwpgYGB7cn0KCmFuYWx5emVfZW5yaWNobWVudCA8LSBmdW5jdGlvbihkZiwgcmVmX3RyZWF0bWVudCA9ICJFbnJpY2hlZCIpIHsKICAjIHJlbGV2ZWwgdGhlIHRyZWF0bWVudCBmYWN0b3IKICBkZiRob3N0X3BoYWdlX3Nwb3IgPC0gcmVsZXZlbChmYWN0b3IoZGYkaG9zdF9waGFnZV9zcG9yKSwgcmVmID0gcmVmX3RyZWF0bWVudCkKICAKICAjIGxvZ2lzdGljIHJlZ3Jlc3Npb246IG1vdGlmIHByZXNlbmNlIH4gdHJlYXRtZW50CiAgbW9kZWwgPC0gZ2xtKHBvcy5QYXJTLmhpdCB+IGhvc3RfcGhhZ2Vfc3BvciwgZmFtaWx5ID0gYmlub21pYWwsIGRhdGEgPSBkZikKICAKICAjIGVzdGltYXRlZCBwcm9iYWJpbGl0aWVzIHBlciB0cmVhdG1lbnQKICBlbW0gPC0gZW1tZWFucyhtb2RlbCwgfiBob3N0X3BoYWdlX3Nwb3IsIHR5cGUgPSAicmVzcG9uc2UiKQogIAogIGxpc3QoCiAgICBtb2RlbF9zdW1tYXJ5ID0gc3VtbWFyeShtb2RlbCksCiAgICBwcm9iYWJpbGl0aWVzID0gZW1tLAogICAgcGFpcndpc2VfdGVzdHMgPSBwYWlycyhlbW0sIGFkanVzdCA9ICJ0dWtleSIpCiAgKQp9CmxpYnJhcnkoZW1tZWFucykKcmVzIDwtIGFuYWx5emVfZW5yaWNobWVudChQYXJTLmJpbiwgcmVmX3RyZWF0bWVudCA9ICJCYWNpbGxvdGFfTHl0aWNfU3BvciIpCgpyZXMkbW9kZWxfc3VtbWFyeSAgICAgIyBsb2dpc3RpYyByZWdyZXNzaW9uIGNvZWZmaWNpZW50cwpyZXMkcHJvYmFiaWxpdGllcyAgICAgIyBlc3RpbWF0ZWQgbW90aWYgcHJvYmFiaWxpdHkgcGVyIHRyZWF0bWVudApyZXMkcGFpcndpc2VfdGVzdHMgICAgIyBhbGwgcGFpcndpc2UgY29tcGFyaXNvbnMKCgpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzJHByb2JhYmlsaXRpZXMpCmhlYWQocHJvYl9kZikKCnByb2JfZGYkaG9zdF9waGFnZV9zcG9yIDwtIHJlb3JkZXIocHJvYl9kZiRob3N0X3BoYWdlX3Nwb3IsIHByb2JfZGYkcHJvYikKCgoKZmlnLm1ldGEgPC0gbWVyZ2UocHJvYl9kZiwgbWV0YS5jYXRzLCBieT0iaG9zdF9waGFnZV9zcG9yIiwgYWxsPVRSVUUpCmBgYAoKYGBge3J9CnBhaXJzX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzJHBhaXJ3aXNlX3Rlc3RzKSAlPiUKICBtdXRhdGUoCiAgICBjb250cmFzdF9jaGFyID0gYXMuY2hhcmFjdGVyKGNvbnRyYXN0KSwKICAgIHBfbGFiZWwgPSBjYXNlX3doZW4oCiAgICAgIHAudmFsdWUgPCAwLjAwMSB+ICIqKioiLAogICAgICBwLnZhbHVlIDwgMC4wMSAgfiAiKioiLAogICAgICBwLnZhbHVlIDwgMC4wNSAgfiAiKiIsCiAgICAgIFRSVUUgICAgICAgICAgICB+ICJucyIKICAgICksCiAgICBncm91cDEgPSBzYXBwbHkoc3Ryc3BsaXQoY29udHJhc3RfY2hhciwgIiAvICIpLCBgW2AsIDEpLAogICAgZ3JvdXAyID0gc2FwcGx5KHN0cnNwbGl0KGNvbnRyYXN0X2NoYXIsICIgLyAiKSwgYFtgLCAyKQogICkKCnBhaXJzX2RmIDwtIGFzLmRhdGEuZnJhbWUocmVzJHBhaXJ3aXNlX3Rlc3RzKSAlPiUKICBtdXRhdGUoCiAgICBjb250cmFzdF9jaGFyID0gYXMuY2hhcmFjdGVyKGNvbnRyYXN0KSwKICAgIHBfbGFiZWwgPSBjYXNlX3doZW4oCiAgICAgIHAudmFsdWUgPiAwLjA1ICAgICAgICAgICAgfiAibnMiLCAgICAgICAgICAgICAgICAgICAgICAjIG5vbi1zaWduaWZpY2FudAogICAgICBwLnZhbHVlIDwgMC4wMDEgICAgICAgICAgIH4gInAgPCAwLjAwMDEiLCAgICAgICAgICAgICAgIyB2ZXJ5IHNtYWxsIHAtdmFsdWVzCiAgICAgIFRSVUUgICAgICAgICAgICAgICAgICAgICAgIH4gcGFzdGUwKCJwID0gIiwgc3ByaW50ZigiJS4zZiIsIHAudmFsdWUpKSAgIyBvdGhlcnMKICAgICksCiAgICBncm91cDEgPSBzYXBwbHkoc3Ryc3BsaXQoY29udHJhc3RfY2hhciwgIiAvICIpLCBgW2AsIDEpLAogICAgZ3JvdXAyID0gc2FwcGx5KHN0cnNwbGl0KGNvbnRyYXN0X2NoYXIsICIgLyAiKSwgYFtgLCAyKQogICkKCgoKCnBhaXJzX2RmIDwtIHBhaXJzX2RmICU+JQogIG11dGF0ZSgKICAgIHhfbnVtMSA9IGFzLm51bWVyaWMoZmFjdG9yKGdyb3VwMSwgbGV2ZWxzID0gbGV2ZWxzKGZpZy5tZXRhJHBoYWdlX3R5cGUpKSksCiAgICB4X251bTIgPSBhcy5udW1lcmljKGZhY3Rvcihncm91cDIsIGxldmVscyA9IGxldmVscyhmaWcubWV0YSRwaGFnZV90eXBlKSkpLAogICAgc3BhbiA9IGFicyh4X251bTEgLSB4X251bTIpLAogICAgeF9wb3MgPSAoeF9udW0xICsgeF9udW0yKSAvIDIKICApICU+JQogIGFycmFuZ2Uoc3BhbikKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyA077iP4oOjIENvbXB1dGUgeSBwb3NpdGlvbnMgZm9yIG5lc3RlZCBicmFja2V0cwpvZmZzZXRfc3RlcCA8LSAwLjAxCgpwYWlyc19kZiA8LSBwYWlyc19kZiAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKAogICAgeV9iYXNlID0gbWF4KAogICAgICBmaWcubWV0YSRhc3ltcC5VQ0xbZmlnLm1ldGEkaG9zdF9waGFnZV9zcG9yICVpbiUgYyhncm91cDEsIGdyb3VwMildLAogICAgICBuYS5ybSA9IFRSVUUKICAgICkKICApICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKHNwYW4sIGRlc2MocF9sYWJlbCkpICU+JSAgIyBzaG9ydGVyIHNwYW5zIGxvd2VyLCBucyBsb3dlcgogIG11dGF0ZSgKICAgIHlfcG9zID0geV9iYXNlICsgKHJvd19udW1iZXIoKSAtIDEpICogb2Zmc2V0X3N0ZXAKICApCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgNe+4j+KDoyBQcmVwYXJlIGNvbXBhcmlzb25zIGxpc3QgZm9yIGdnc2lnbmlmCmNvbXBhcmlzb25zX2xpc3QgPC0gbGFwcGx5KDE6bnJvdyhwYWlyc19kZiksIGZ1bmN0aW9uKGkpIHsKICBjKGFzLmNoYXJhY3RlcihwYWlyc19kZiRncm91cDFbaV0pLCBhcy5jaGFyYWN0ZXIocGFpcnNfZGYkZ3JvdXAyW2ldKSkKfSkKCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyA277iP4oOjIFBsb3Qgd2l0aCBkb2RnZSBhbmQgYmxhY2sgYnJhY2tldHMKcGQgPC0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjUpCgp0cGxvdCA8LSBnZ3Bsb3QoZmlnLm1ldGEsIGFlcyh4ID0gaG9zdF9waGFnZV9zcG9yLCB5ID0gcHJvYiwgY29sb3IgPSBsaWZlc3R5bGUpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMywgcG9zaXRpb24gPSBwZCkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBhc3ltcC5MQ0wsIHltYXggPSBhc3ltcC5VQ0wpLCB3aWR0aCA9IDAuMiwgcG9zaXRpb24gPSBwZCkgKwogIGdnc2lnbmlmOjpnZW9tX3NpZ25pZigKICAgIGNvbXBhcmlzb25zID0gY29tcGFyaXNvbnNfbGlzdCwKICAgIGFubm90YXRpb25zID0gcGFpcnNfZGYkcF9sYWJlbCwKICAgIHlfcG9zaXRpb24gPSBwYWlyc19kZiR5X3BvcywKICAgIHRpcF9sZW5ndGggPSAwLjAyLAogICAgdGV4dHNpemUgPSAzLjUsCiAgICBjb2xvciA9ICJibGFjayIKICApICsKICB5bGltKDAsIG1heChwYWlyc19kZiR5X3BvcyArIDAuMDIpKSArICAjIGV4dGVuZCB5LWF4aXMgdG8gZml0IHRvcCBicmFja2V0cwogICB5bGFiKCJQcm9iYWJpbGl0eSBvZiAxIG9yIG1vcmUgUGFyUyBcbiBiaW5kaW5nIHNpdGVzIGluIHBoYWdlIGdlbm9tZSIpICsKICB4bGFiKCIiKSArCiAgbGFicyhjb2xvciA9ICJQaGFnZSBMaWZlc3R5bGUiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNCkrc2NhbGVfeF9kaXNjcmV0ZSgKICAgIGxhYmVscyA9IGMoCiAgICAgICdMeXRpY19TcG9yJyAgICA9ICdMeXRpYycsCiAgICAgICdUZW1wX1Nwb3InICAgICA9ICdUZW1wZXJhdGUnLAogICAgICAnTHl0aWNfTm9uU3BvcicgPSAnTHl0aWMnLAogICAgICAnVGVtcF9Ob25TcG9yJyAgPSAnVGVtcGVyYXRlJwogICAgKQogICkgKyB0aGVtZShwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDMwLCAxMCkgICMgbGFyZ2UgYm90dG9tIG1hcmdpbiBmb3IgbGFiZWxzCiAgKSArCiAgY29vcmRfY2FydGVzaWFuKGNsaXAgPSAib2ZmIikKCnRwbG90CgoKc3Bvcl9sYWJlbCA8LSB0ZXh0R3JvYigKICAiU3BvcnVsYXRpbmcgSG9zdCIsIGdwID0gZ3Bhcihmb250c2l6ZSA9IDE0LCBmb250ZmFjZSA9ICJib2xkIiksCiAgeCA9IDAuMjUsIHkgPSAtMC4xMywganVzdCA9ICJjZW50ZXIiCikKCm5vbnNwb3JfbGFiZWwgPC0gdGV4dEdyb2IoCiAgIk5vbi1TcG9ydWxhdGluZyBIb3N0IiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTQsIGZvbnRmYWNlID0gImJvbGQiKSwKICB4ID0gMC43NSwgeSA9IC0wLjEzLCBqdXN0ID0gImNlbnRlciIKKQoKCnRwbG90X2ZpbmFsIDwtIHRwbG90ICsKICBhbm5vdGF0aW9uX2N1c3RvbShzcG9yX2xhYmVsKSArCiAgYW5ub3RhdGlvbl9jdXN0b20obm9uc3Bvcl9sYWJlbCkKCnRwbG90X2ZpbmFsCgoKYGBg